package lockforredis.common;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.Expiration;

import java.util.*;

/**
 * redis分布式锁基础类
 *
 * @author: 顾志杰
 */
@Slf4j
public class RedisLock implements AutoCloseable{

    private RedisTemplate redisTemplate;
    /**
     * 根据业务命名key
     */
    private String key;
    /**
     * uuid自动生成value
     */
    private String value;
    /**
     * 过期时间 单位：秒
     */
    private int expireTime;


    public RedisLock(RedisTemplate redisTemplate, String key, int expireTime){
        this.redisTemplate = redisTemplate;
        this.key = key;
        this.expireTime=expireTime;
        this.value = UUID.randomUUID().toString();
    }


    /**
     * 获取分布式锁
     * @return
     */
    public boolean getLock(){
        RedisCallback<Boolean> redisCallback = connection -> {
            //设置NX
            RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
            //设置过期时间
            Expiration expiration = Expiration.seconds(expireTime);
            //序列化key
            byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
            //序列化value
            byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
            //执行setnx操作
            Boolean result = connection.set(redisKey, redisValue, expiration, setOption);

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return result;
        };

        //获取分布式锁
        Boolean lock = (Boolean)redisTemplate.execute(redisCallback);
        if(lock){
            this.autoRefreshLockTimes(key,value,30);
        }
        return lock;
    }

    /**
     * 关闭锁
     * @return
     */
    public boolean unLock() {

        /*Lua脚本 当脚本正在运行的时候，不会有其他脚本或 Redis 命令被执行 判断关闭锁*/
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
                "    return redis.call('del',KEYS[1])\n" +
                "else " +
                "    return 0 " +
                "end";
        RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class);
        List<String> keys = Arrays.asList(key);
        Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value);
        log.info("释放锁的结果："+result);
        return result;
    }

    private Timer lockTimer = new Timer();
    /**
     * 自动续期
     * @param distLock 锁key
     * @param selfId 锁值
     * @param expireTimes 续期时间
     */
    private void autoRefreshLockTimes(String distLock, String selfId, Integer expireTimes) {

        // if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('expire',KEYS[1],30) else return 0 end

        String refreshScript =
                " if redis.call('get',KEYS[1]) == ARGV[1] "
                        + " then "
                        +   " return redis.call('expire',KEYS[1],30) "
                        + " else "
                        +   " return 0 "
                        + " end "
                ;
        RedisScript<Boolean> redisScript = RedisScript.of(refreshScript,Boolean.class);
        List<String> keys = Arrays.asList(distLock);
        lockTimer.schedule(new TimerTask() {
                               @Override
                               public void run() {
                                   System.out.println("自动续期，重置到30秒");
                                   redisTemplate.execute(redisScript,keys , selfId);
                               }
                           },
                expireTimes/3*1000, //锁开始的3分之一时间开始检测
                expireTimes/3*1000);//多长时间检测一次
    }


    /**
     *  继承AutoCloseable类
     *  try {
     *
     *         }catch (){
     *
     *         }finally {
     *      相当于这个部分代码块
     *         }
     * @throws Exception
     */
    @Override
    public void close() throws Exception {
        if(unLock()){
            lockTimer.cancel();
        };
    }
}
